﻿/*
 * Created by SharpDevelop.
 * User: Corrado Mio
 * Date: 02/06/2012
 * Time: 18:08
 * 
 * Eccezioni sulle socket:
 * 
 * http://msdn.microsoft.com/en-us/library/windows/desktop/ms740668%28v=vs.85%29.aspx
 * 
 * TcpClient e' una classe ad utilizzo singolo. Una volta chiamata la Close(), 
 * non puo' piu' essere utilizzata.
 * 
 * Sono state aggiunte le due classi
 * 
 *      TcpServer  manage  TcpListener, TcpConnect
 *      TcpConnect extends TcpClient
 * 
 * Messaggi
 * --------
 * 
 * Al protocollo TCP e' stato aggiunto gia' a questo livello il supporto
 * per l'invio e la ricezione di 'messaggi'.
 * 
 * Un messaggio e' composto da un header di 4 byte e da un body di, al massimo, 
 * 65531 byte (per un totale di 65535 byte)
 * 
 *      [header:4]      4 byte.         Intestazione del messaggio
 *      [data:length]   length byte     Corpo del messaggio.
 * 
 * L'header e' compostp da due perti: i 2 byte bassi contengono la dimensione
 * del messaggio (del SOLO messaggio), mentre i 2 byte alti vengono utilizzati
 * per controllare la correttezza del messaggio
 * 
 *      [check:2][length:2]
 * 
 *      check = 0xFFFF ^ length
 * 
 */
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;

using CSharpExt.Logging;
using CSharpExt.Threading;
using log4net;

namespace CSharpExt.Net.Sockets
{
    // -----------------------------------------------------------------------
    // TcpListener
    // -----------------------------------------------------------------------
    
    /// <summary>
    /// Estende 'System.Net.Sockets.TcpListener' al solo scopo 
    /// di rendere visibile il flag 'protected bool Active', con nome 'Listening'
    /// </summary>
    public class TcpListenerEx : System.Net.Sockets.TcpListener
    {
        #region Constructor
        
        public TcpListenerEx(IPEndPoint ep) : base(ep)
        {
            
        }
        
        public TcpListenerEx(IPAddress address, int port) : base(address, port)
        {
            
        }
        
        #endregion
        
        #region Properties
        
        /// <summary>
        /// Se il listener e' in ascolto
        /// </summary>
        public bool Listening
        {
            get { return Active; }
        }
        
        #endregion
    }
    
    
    // -----------------------------------------------------------------------
    // TcpConnect
    // -----------------------------------------------------------------------
    
    /// <summary>
    /// Estende TcpClient con le seguenti funzionalita':
    /// 
    /// - rimossi i costruttori che fanno una connessione automatica.
    ///   La connessione deve essere fatta con Open() o Connect() (sono
    ///   sinonimi)
    /// 
    /// - mette a disposizione le operazioni Send() e Receive()
    /// 
    /// - le operazione Send() e Receive() NON generano eccezione se la connessione
    ///   non e' stata stabilita
    /// 
    /// - se una Send() o una Receive() generano un'eccezione, forse e' il caso di 
    ///   abortire la socket?
    /// 
    /// - mette a disposizione le callback 'OnReceived()' e 'OnReceivedFrom()' per
    ///   implementare la ricezione su un thread separato. Le callback devono essere
    ///   utilizzate in alternativa, non contemporaneamente
    /// 
    /// - un'eccezione nella ricezione asincrona chiude la connessione, e l'eccezione intercettata
    ///   viene salvata in 'CatchedException'
    /// 
    /// - la semantica della Close() non e' stata modificata. Ci sono due possibili 
    ///   implementazioni:
    /// 
    ///     Close() -> Dispose() -> virtual Dispose(bool)
    ///     Close() -> virtual Dispose(bool)
    /// 
    ///   e' stata ridefinita la virtual Dispose(bool) affinche' chiami la OnClose(), 
    ///   ma con la seguente funzionalita': la Close() puo' essere chiamata in modo
    ///   concorrente, ma solo un thread la esegue
    /// 
    /// - la Open() e la Connect() fanno le opportune inizializzazioni sulla socket
    ///   quindi chiamano la OnOpen().
    ///   Questo metodo puo' e' quello che fa le opportune inizializzazioni alla
    ///   claase. Questo metodo e' anche quello chiamato quando la classe TcpConnect
    ///   viene creata a partire da una socket generata dalla TcpListener (usata in
    ///   TcpServer)
    /// 
    /// 
    /// Note sul funzionamento:
    /// 
    /// - se viene generata un'eccezione nelle 'OnReceived()':
    /// 
    ///   -- viene salvata l'eccezione
    ///   -- viene chiusa la connessione
    ///   -- viene terminato il thread
    /// 
    /// - il buffer passato nelle callback 'OnReceived(...)' E' SEMPRE LO STESSO, 
    ///   quindi, se necessario, il suo contenuto DEVE essere copiato in un'altro buffer
    /// 
    /// Todo: usare un flag per specificahe di utilizzare buffer sempre nuovi?
    /// 
    /// </summary>
    public class TcpConnect : TcpClient
    {
        #region Private Data
        
        protected EndPoint _endPoint;
        
        protected Exception _exception;
        
        protected Disposed _disposed = new Disposed();
        
        private bool _messages = true;
        
        private Thread _receiveThread;
        
        // ------------------------------------------------------------------
        // Message support
        //
        // Un 'messaggio' e' composto da un HEADER di 4 byte:
        //
        //  2 per indicare la lunghezza del body (limite a 65531 byte)
        //  2 come checksum
        //
        //      [checksum:2][count:2][body:count]
        //
        // il checksum e' calcolato nel seguente modo:
        //
        //      0xFFFF ^ _count
        //
        //
        
        private const int CHECK = 0xFFFF;
        private const int HEADER_SIZE = 4;
        private const int MAX_MESSAGE_SIZE = 65535 - HEADER_SIZE;
        
        private byte[] _header = new byte[HEADER_SIZE];
        private byte[] _buffer = new byte[2*(MAX_MESSAGE_SIZE + HEADER_SIZE)];
        
        private int _offset, _count;
        
        #endregion
        
        #region Constructor
        
        /// <summary>
        /// Unico costruttore.
        /// 
        /// La connessione deve essere fatta chiamando Connect()
        /// </summary>
        public TcpConnect()
        {
            
        }
        
        #endregion
        
        #region Properties
        
        public Socket Socket
        {
            get { return base.Client; }
        }
        
        /// <summary>
        /// EndPoint a cui collegarsi (puo' essere un IPEndPoint o un DnsEndPoint)
        /// </summary>
        public EndPoint EndPoint
        {
            get { return _endPoint;  }
            set { _endPoint = value; }
        }
        
        /// <summary>
        /// Controlla se e' connesso ad un server remoto
        /// 
        /// Nota: il metodo originiale generava un NullPointerException
        /// dopo aver chiamato Close()
        /// </summary>
        public new bool Connected
        {
            get { return Client != null && Client.Connected; }
        }
        
        /// <summary>
        /// Controlla se e' in ascolto su una porta locale
        /// 
        /// Nota: il metodo originale generava un NullPointerException
        /// dopo aver chiamato Close()
        /// </summary>
        /// </summary>
        public bool IsBound
        {
            get { return Client != null && Client.IsBound; }
        }
        
        /// <summary>
        /// Eventuale eccezione intercettata in fase di Connect() 
        /// ed in fase di Receive asincrona
        /// </summary>
        public Exception CatchedException
        {
            get { return _exception; }
        }
        
        /// <summary>
        /// Invia o riceve messaggi.
        /// 
        /// Default: true
        /// </summary>
        public bool Messages
        {
            get { return _messages;  }
            set { _messages = value; }
        }
        
        // ------------------------------------------------------------------
        // E' possibile attivare una ricezione asincrona
        // Se la ricezione fallisce, il thread viene terminato ed il socket 
        // viene chiuso (viene chiamata la Close() ).
        // 
        // Se la ricezione fallisce per timeout, viene chiamata la
        //
        //  OnReceiveTimeout
        //
        
        /// <summary>
        /// Callback per la ricezione asincrona.
        /// 
        /// Se inizializzato, viene creato un thread per la ricezione
        /// 
        /// Nota: il buffer passato E' SEMPRE lo stesso
        /// </summary>
        public Action<byte[], int, int> OnReceived;
        
        public Action<TcpConnect, byte[], int, int> OnReceivedFrom;
        
        public Action<TcpConnect> OnReceivedTimeout;
        
        /// <summary>
        /// Chiamato nella ricezione asincrona in caso di eccezione.
        /// 
        /// Subito dopo viene chiamata la Close()
        /// </summary>
        public Action<TcpConnect, Exception> OnException;
        
        #endregion
        
        #region Operations
        
        /// <summary>
        /// Si connette ad EndPoint
        /// 
        /// Fino a che non ci riesce, la socket e' ancora valida
        /// 
        /// Nota:  Open() e' un alias di Connect()
        /// </summary>
        /// <returns></returns>
        public void Connect()
        {
            if (_endPoint as DnsEndPoint != null)
            {
                DnsEndPoint dnsep = _endPoint as DnsEndPoint;
                base.Connect(dnsep.Host, dnsep.Port);
            }
            else
            {
                IPEndPoint ipep = _endPoint as IPEndPoint;
                base.Connect(ipep.Address, ipep.Port);
            }
            
            OnOpen();
        }
        
        public void Open() { Connect(); }
        
        /// <summary>
        /// Tenta di connetersi ad EndPoint.
        /// 
        /// Se non ci riesce ritorna false e in CatchedException e' 
        /// disponibile l'eventuale eccezione che non ha permesso la
        /// connessione
        /// </summary>
        /// <returns></returns>
        public bool TryConnect()
        {
            try
            {
                Connect();
                
                return true;
            }
            catch (Exception e)
            {
                onException(e);
                
                return false;
            }
        }
        
        /// <summary>
        /// Tenta di connettersi entro un determinato intervallo di tempo
        /// 
        /// Se non ci riesce ritorna false e in CatchedException e' 
        /// disponibile l'eventuale eccezione che non ha permesso la
        /// connessione.
        /// 
        /// Viene chiamata la Close()
        /// </summary>
        /// <param name="timeout"></param>
        /// <returns></returns>
        public bool TryConnect(int timeout)
        {
            bool connected = false;
            try
            {
                IAsyncResult ar;
                
                if (_endPoint as DnsEndPoint != null)
                {
                    DnsEndPoint dnsep = _endPoint as DnsEndPoint;
                    ar = base.BeginConnect(dnsep.Host, dnsep.Port, null, null);
                }
                else
                {
                    IPEndPoint ipep = _endPoint as IPEndPoint;
                    ar = base.BeginConnect(ipep.Address, ipep.Port, null, null);
                }
                
                connected = ar.AsyncWaitHandle.WaitOne(timeout);
                
                if (!connected)
                    _exception = new TimeoutException(String.Format("Unable to connect to {0} in {1} ms", EndPoint, timeout));
                else
                    EndConnect(ar);
            }
            catch (Exception e)
            {
                onException(e);
            }
            
            return connected;
        }
        
        /// <summary>
        /// 
        /// Nota: se l'EndPoint non e' valido, viene generata un'eccezione
        /// </summary>
        /// <param name="callback"></param>
        /// <param name="state"></param>
        /// <returns></returns>
        public IAsyncResult BeginConnect(AsyncCallback callback, Object state)
        {
            if (_endPoint as DnsEndPoint != null)
            {
                DnsEndPoint dnsep = _endPoint as DnsEndPoint;
                return base.BeginConnect(dnsep.Host, dnsep.Port, callback, state);
            }
            else
            {
                IPEndPoint ipep = _endPoint as IPEndPoint;
                return base.BeginConnect(ipep.Address, ipep.Port, callback, state);
            }
        }
        
        public new void EndConnect(IAsyncResult asyncResult)
        {
            base.EndConnect(asyncResult);
        }
        
        
        public void Disconnect()
        {
            Client.Shutdown(SocketShutdown.Both);
            Client.Disconnect(false);
        }
        
        // ------------------------------------------------------------------
        // Overridables
        // ------------------------------------------------------------------
        
        protected virtual void OnOpen()
        {
            if (OnReceived != null)
                OnReceivedFrom = onReceivedFrom;
               
            if (OnReceivedFrom != null)
                Start(); 
        }
        
        protected virtual void OnClose()
        {
            base.Dispose(true);
            
            // la Stop va chiamata dopo la OnClose() per far si
            // che il thread termini per un'eccezione nella socket
            Stop();
        }
        
        /// <summary>
        /// Invia il buffer
        /// 
        /// Nota: se il client non e' connesso, NON GENERA un'eccezione
        /// 
        /// Nota: si assicura che la Send possa essere utilizzata in un
        /// contesto concorrente
        /// </summary>
        public void Send(byte[] buffer, int offset, int count)
        {
            if (Connected)
            {
                lock (_header)
                {
                    if (_messages)
                        sendMessage(buffer, offset, count);
                    else
                        sendBuffer(buffer, offset, count);
                }
            }
        }
        
        /// <summary>
        /// Riceve un buffer
        /// 
        /// Nota: se il client non e' connesso, NON GENERA un'eccezione, e ritorna
        /// 0. 
        /// Per default, non ha senso che la Receive ritorni 0
        /// </summary>
        public int Receive(byte[] buffer, int offset, int count)
        {
            int length = 0;
            
            if (Connected)
            {
                SocketError errorCode;
                
                length = Client.Receive(buffer, offset, count, SocketFlags.None, out errorCode);
                
                if (errorCode != SocketError.Success)
                    throw new SocketException((int)errorCode);
                
                if (length == 0)
                    throw new SocketException(10054/*Connection reset by peer*/);
            }
            
            return length;
        }
         
        // ------------------------------------------------------------------
        
        protected Thread CreateThread(ThreadStart/*Action*/ onReceive)
        {
            return new Thread(onReceive);
        }
        
        #endregion
        
        #region Implementation
        
        private void sendBuffer(byte[] buffer, int offset, int count)
        {
            if (count > MAX_MESSAGE_SIZE)
                throw new SocketException(10053);
            
            SocketError errorCode;
                
            Client.Send(buffer, offset, count, SocketFlags.None, out errorCode);
            
            if (errorCode != SocketError.Success)
                throw new SocketException((int)errorCode);
        }
        
        private void sendMessage(byte[] buffer, int offset, int count)
        {
            if (count > MAX_MESSAGE_SIZE)
                throw new SocketException(10053);
            
            int marker = CHECK ^ count;
            
            _header[0] = (byte)(count  >> 0);
            _header[1] = (byte)(count  >> 8);
            _header[2] = (byte)(marker >> 0);
            _header[3] = (byte)(marker >> 8);
            
            sendBuffer(_header, 0, 4);
            sendBuffer(buffer, offset, count);
        }
        
        // ------------------------------------------------------------------
    
        private void Start()
        {
            _receiveThread = CreateThread(receive);
            _receiveThread.Start();
        }
        
        private void Stop()
        {
            if (_receiveThread != null && !_receiveThread.Join(1000))
                _receiveThread.Abort();
            _receiveThread = null;
        }
        
        private void receive()
        {
            try
            {
                while(Connected)
                {
                    try
                    {
                        int readed = Receive(_buffer, _offset, 65536);
                        
                        if (readed == 0)
                            throw new SocketException(10053/*Socket reset by peer*/);
                        
                        if (_messages)
                            onReceivedMessage(readed);
                        else
                            onReceivedBuffer(readed);
                        
                    }
                    catch (Exception e)
                    {
                        // "A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond"
                        if (e.Message.IndexOf("not properly respond after a period of time") != -1)
                        {
                            if (OnReceivedTimeout != null)
                                OnReceivedTimeout(this);
                        }
                        else
                        {
                            if (OnException != null)
                                OnException(this, e);
                            
                            break;
                        }
                    }
                }
            }
            finally
            {
                Close();
            }
        }
        
        private void onReceivedBuffer(int readed)
        {
            OnReceivedFrom(this, _buffer, 0, readed);
        }
        
        // [header:2][data:n]
        private void onReceivedMessage(int readed)
        {
            _offset += readed;
            
            while (_offset > 0)
            {
                int marker = 0;
                
                if (_offset < HEADER_SIZE)
                    return;         // non ci sono abbastanza byte per leggere lo HEADER
                
                if (_count == 0)
                {
                    _count = ((_buffer[0] << 0) + (_buffer[1] << 8));
                    marker = ((_buffer[2] << 0) + (_buffer[3] << 8));

                    // controlla se ha letto una lunghezza sbagliata del buffer
                    if (_count < 0 || _count > MAX_MESSAGE_SIZE)
                        throw new SocketException(10053);
                    
                    // controlla se ha letto 4 byte a casaccio
                    if ((CHECK ^ _count) != marker)
                        throw new SocketException(10053);
                }
                
                if (_offset < (HEADER_SIZE + _count))
                    return;         // non ci sono abbastanza byte per leggere il body
                
                // ci sono abbastanza byte per leggere il body
                OnReceivedFrom(this, _buffer, HEADER_SIZE, _count);
                
                // byte rimanenti
                int rest = _offset - (HEADER_SIZE + _count);
                
                // sposta i byte rimanenti in testa al buffer per la prossima elaborazione
                if (rest > 0)
                    Buffer.BlockCopy(_buffer, _count + HEADER_SIZE, _buffer, 0, rest);
                
                // reinizzializza _count e _offset
                _count = 0;
                _offset = rest;
            }
        }
        
        // ------------------------------------------------------------------
        
        private void onReceivedFrom(TcpConnect client, byte[] buffer, int offset, int count)
        {
            OnReceived(buffer, offset, count);
        }
        
        // ------------------------------------------------------------------
        
        /// <summary>
        /// NON fare l'override di questo metodo, ma di OnClose()
        /// </summary>
        /// <param name="disposing"></param>
        protected override void Dispose(bool disposing)
        {
            if(_disposed.CanDispose)
                OnClose();
        }
        
        private void onException(Exception e)
        {
            _exception = e;
            
            if (OnException != null)
                OnException(this, e);
        }
        
        #endregion
        
        #region TcpServer Support
        
        public TcpConnect(Socket socket)
        {
            Client = socket;
            Active = true;
            
            _endPoint = socket.RemoteEndPoint;
        }
        
        public void OnAccepted()
        {
            OnOpen();
        }
        
        #endregion
    }
    
    
    // ----------------------------------------------------------------------
    // TcpServer
    // ----------------------------------------------------------------------
    // La classe TcpServer ha il compito di coordinare in modo piu' completo
    // la TcpListener e TcpClient.
    // In particlare:
    //  
    //  - invece della tcpListener, viene utilizzata la TcpConnect che mette a 
    //    disposizione molte piu' funzionalita'
    //  - tenete traccia di tutti i client che si sono collegati
    //  - controlla anche se qualche client non risponde piu', nel qual caso
    //    lo disconnette automaticamente
    //  - permette di fare la Send() su tutti i client collegati
    //  - permette di registrare una callback per la ricezione che tutti i
    //    client possono utilizzare
    //
    
    /// <summary>
    /// Ha il compito di coordinare una TcpListener con i client che si connettono
    /// e disconnettono.
    /// 
    /// Mette a disposizione le seguenti funzionalita':
    /// 
    /// - tiene traccia dei client che si connettono e disconnettono
    /// 
    /// - mette a disposizione una 'Send()' che invia il buffer a tutti i client
    ///   collegati
    /// 
    /// - mette a disposizione delle callback 'OnReceived()' ed 'OnReceivedFrom()'
    ///   che tutti i client possono chiamare in modo asincrono alla ricezione di
    ///   dati
    /// 
    /// - mette a disposizione la callback 'OnClient()' chiamata ogni volta che un
    ///   client si connette o si disconnette.
    ///   Chiamata quando il client e' connesso e prima di disconnetersi
    /// 
    /// Note:
    /// 
    /// - se una 'Send(...)' verso un client fallisce, il client viene chiuso
    /// - se una 'OnReceive(...)' di un client fallisce, il client viene chiuso
    /// 
    /// </summary>
    public class TcpServer
    {
        #region Private Data
        
        private TcpListenerEx _listener;
        
        private IPEndPoint _endPoint;
        
        private bool _messages = true;
        
        private volatile List<TcpConnect> _clients = new List<TcpConnect>();
        
        private Thread _listenThread;
        
        private Exception _exception;
        
        private int _listenTimeout = 1000;
        
//        private int _receiveTimeout;
        
//        private int _sendTimeout;
        
        protected Disposed _disposed = new Disposed();
        
        #endregion
        
        #region Constructor
        
        public TcpServer()
        {

        }
        
        #endregion
        
        #region Properties
        
        public Socket Socket
        {
            get { return _listener.Server; }
        }
        
        /// <summary>
        /// EndPoint su cui fare la Listen.
        /// </summary>
        public IPEndPoint EndPoint 
        {
            get { return _endPoint;  }
            set { _endPoint = value; }
        }
        
        public int ListenTimeout
        {
            get { return _listenTimeout;  }
            set { _listenTimeout = value; }
        }
        
//        public int ReceiveTimeout
//        {
//            get { return _receiveTimeout;  }
//            set { _receiveTimeout = value; }
//        }
        
//        public int SendTimeout
//        {
//            get { return _sendTimeout;  }
//            set { _sendTimeout = value; }
//        }
        
        /// <summary>
        /// Se deve ricevere, inviare 'messaggi'
        /// Ogni send e' vista come un messaggio
        /// </summary>
        public bool Messages
        {
            get { return _messages;  }
            set { _messages = value; }
        }
        
        // ----------------------------------------------------------------
        // Gestione della listen
        
        /// <summary>
        /// Se il listener e' in ascolto
        /// 
        /// Nota: se il listener non e' in ascolto, tentare una 'Start()'
        /// </summary>
        public bool Listening
        {
            get { return _listener != null && _listener.Listening; }
        }
        
        /// <summary>
        /// Chiamato quando viene rilevato un timeout nella listen
        /// </summary>
        public Action OnListenTimeout;
        
        /// <summary>
        /// Callback chiamata nel caso in cui la listen fallisca.
        /// 
        /// Nota: subito dopo viene chiama la 'Stop()'
        /// </summary>
        public Action<TcpServer, Exception> OnException;

        
        // ----------------------------------------------------------------
        // Gestione dei client
        
        /// <summary>
        /// Ritorna l'elenco dei client correntemente collegati
        /// </summary>
        public List<TcpConnect> Clients
        {
            get { return _clients; }
        }
        
        /// <summary>
        /// Chiama ogni volta che un nuovo client si e' connesso (True), o
        /// disconnesso (False)
        /// </summary>
        public Action<TcpConnect, bool> OnClient;
        
        /// <summary>
        /// Callback chiamata nel caso in cui avvenga un'eccezione
        /// nella gestione di un client.
        /// 
        /// Subito dopo il client viene chiuso
        /// </summary>
        public Action<TcpConnect, Exception> OnClientException;
        
        
        // ------------------------------------------------------------------
        // E' possibile attivare una ricezione asincrona su ogni client che
        // si collega.
        // Se la ricezione fallisce, il corrispondete client viene 'Disconnected()' 
        // 
        
        /// <summary>
        /// Callback chiamate dai client alla ricezione di un buffer.
        /// 
        /// Nota: devono essere utilizzate IN ALTERNATIVA, non contemporaneamnete
        /// </summary>
        public Action<byte[], int, int> OnReceived;
        
        public Action<TcpConnect, byte[], int, int> OnReceivedFrom;
        
        public Action<TcpConnect> OnReceiveTimeout;
        
        #endregion
        
        #region Operations
        
        public void Open(){ Listen(); }
        
        public void Listen()
        {
            StartListener();
        }
        
        public void Close()
        {
           if(_disposed.CanDispose)
                OnClose();
        }
        
        protected virtual void OnClose()
        {
            StopListener();
            closeClients();
        }
        
        // ------------------------------------------------------------------
        
        /// <summary>
        /// Invia il buffer a tutti i client collegati
        /// 
        /// Nota: se la send verso un client fallisce, sul client viene chiamata
        /// la Close()
        /// </summary>
        public void Send(byte[] buffer, int offset, int count)
        {
            foreach(TcpConnect client in Clients)
            {
                try
                {
                    client.Send(buffer, offset, count);
                }
                catch(Exception e)
                {
                    onClientException(client, e);
                    
                    closeClient(client);
                }
            }
        }
        
        #endregion
        
        #region Implementation
        
        protected TcpListenerEx CreateListener(IPEndPoint listenOn)
        {
            return new TcpListenerEx(listenOn); 
        }
        
        protected Thread CreateThread(ThreadStart listen)
        {
            return new Thread(listen);
        }
        
        protected TcpConnect CreateClient(Socket socket)
        {
            return new TcpConnect(socket);
        }
        
        // ------------------------------------------------------------------
        
        private void StartListener()
        {
            _listener = CreateListener(_endPoint);
            _listener.Start();
            
            _listenThread = CreateThread(listen);
            _listenThread.Start();
        }
        
        private void StopListener()
        {
            _listener.Stop();
            
            if(_listenThread != null & !_listenThread.Join(1000))
                _listenThread.Abort();
            _listenThread = null;
        }
        
        private void listen()
        {
            try
            {
                while(Listening)
                {
                    Socket socket;
                    TcpConnect client = null;
                    
                    // controlla le eccezioni sulla Accept
                    // se viene generata un'eccezione, chiude
                    // il listener
                    try
                    {
                        IAsyncResult ar = _listener.BeginAcceptSocket(null, null);
                        
                        // cicla DUE volte al secondo e controlla lo stato dei client
                        // i client in errore, li disconnette
                        while(Listening && !ar.AsyncWaitHandle.WaitOne(_listenTimeout))
                        {
                            checkClients();
                            
                            if (OnListenTimeout != null)
                                OnListenTimeout();
                        }
                        
                        if (!Listening)
                            continue;
                        
                        socket = _listener.EndAcceptSocket(ar);
                    }
                    catch (Exception e)
                    {
                        onException(e);
                        
                        break;
                    }
                    
                    // controlla le eccezioni sul client
                    // se viene generata un'eccezione, chiude SOLO il client
                    try
                    {
                        client = CreateClient(socket);
                        
                        initClient(client);
                        
                        addClient(client);
                    }
                    catch(Exception e)
                    {
                        // non e' stata chiamata la 'addClient()'
                        
                        onClientException(client, e);
                        
                        closeClient(client);
                    }
                }
            }
            catch (Exception e)
            {
                onException(e);
            }
            finally
            {
                Close();
            }
        }
        
        // ------------------------------------------------------------------
        
        private void initClient(TcpConnect client)
        {
            client.Messages = _messages;

//            if (_receiveTimeout != 0)
//                client.ReceiveTimeout = _receiveTimeout;

            // il client non e' stato ancora inizializzato completamete
            // l'inizializzazione verra' completata quando verra' chiamata
            // la TcpConnect.OnAccepted() che a sua volta chiama la
            // TcpClient.OnOpen()
            //
            // Ulteriori inizializzazioni, prima della OnOpen(), possono
            // essere fatte nell'implemenazione della callback OnClient
            // che viene chiamata PRIMA della TcpClient.OnAccepted()
            
            if (client.OnReceived == null)
                client.OnReceived = OnReceived;
            
            if (client.OnReceivedFrom == null)
                client.OnReceivedFrom = OnReceivedFrom;
            
            if (client.OnException == null)
                client.OnException = OnClientException;
            
            if (OnClient != null)
                OnClient(client, true);
            
            client.OnAccepted();
        }
        
        private void addClient(TcpConnect client)
        {
            lock (_clients)
            {
                _clients = new List<TcpConnect>(_clients);
                _clients.Add(client);
            }
        }
        
        private void removeClient(TcpConnect client)
        {
            lock (_clients)
            {
                _clients = new List<TcpConnect>(_clients);
                _clients.Remove(client);
            }

            if (OnClient != null)
                OnClient(client, false);
            
        }
        
        private void closeClient(TcpConnect client)
        {
            removeClient(client);

            client.Close();
        }
        
        private void checkClients()
        {
            lock (_clients)
            {
                foreach(TcpConnect client in Clients)
                {
                    if (!client.Connected)
                        removeClient(client);
                }
            }
        }
        
        private void closeClients()
        {
            foreach(TcpConnect client in Clients)
            {
                closeClient(client);
            }
        }
        
        // ------------------------------------------------------------------
        
        private void onException(Exception e)
        {
            _exception = e;
            
            if (OnException != null)
                OnException(this, e);
        }
        
        private void onClientException(TcpConnect c, Exception e)
        {
            if (OnClientException != null)
                OnClientException(c, e);
        }
        
        #endregion
    }

}

